<template>
	<div ref="wrap" class="snow-wrap" v-if="myShow">
		<img v-for="(img,index) in imgs" :key="index" :src="img" style="display:none;" class="lis_flake">
		<canvas ref="canvas" width="100%" height="100%"></canvas>
	</div>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";

@Component({})
export default class Snow extends Vue {
	public imgs: string[] = [
		"https://raw.githubusercontent.com/bob-chen/let_it_snow/master/demo/snow.png",
		"https://raw.githubusercontent.com/bob-chen/let_it_snow/master/demo/sock.png",
		"https://raw.githubusercontent.com/bob-chen/let_it_snow/master/demo/tree.png"
	]; // 图标列表
	public canvas: any = null; // canvas控件
	public ctx: any;
	public myShow: boolean = true; // 是否显示页面
	private speed: number = 3; // 下落速度
	private interaction: boolean = false; // 是否相互影响
	private flakes: any = []; // icon数目
	private size: number = 10;
	private count: number = 30;
	private opacity: number = 1;
	private color: string = "#ffffff";
	private windPower: any = 1;
	private resizeTO: any;
	private reqId: any = null; // 开启下雪
	private mX: number = -100;
	private mY: number = -100;

	private imageItems: any = [];
	private imageNum: any = [];

	// 最先开始执行
	mounted() {
		let requestAnimationFrame =
			window.requestAnimationFrame ||
			(callback => {
				return window.setTimeout(callback, 1000 / 60);
			});
		window.requestAnimationFrame = requestAnimationFrame;
		let cancelAnimationFrame =
			window.cancelAnimationFrame ||
			(id => {
				window.clearTimeout(id);
			});
		window.cancelAnimationFrame = cancelAnimationFrame;

		window.onresize = () => {
			if (this.resizeTO) clearTimeout(this.resizeTO);
			this.resizeTO = setTimeout(() => {
				// this.imgs = [];
				this.init();
			}, 200);
		};
		this.init();
	}
	private init(): void {
		if (this.reqId) cancelAnimationFrame(this.reqId);

		this.canvas = this.$refs.canvas;
		this.ctx = this.canvas.getContext("2d");
		this.canvas.width = window.innerWidth;
		this.canvas.height = window.innerHeight;

		if (this.interaction) {
			let actionFuncName =
				"ontouchstart" in document.documentElement
					? "touchmove"
					: "mosemove";
			this.canvas.addEventListenter(actionFuncName, (e: any) => {
				if (actionFuncName === "touchmove") {
					this.mX = e.touches[0].clientX;
					this.mY = e.touches[0].clientY;
				} else {
					this.mX = e.clientX;
					this.mY = e.clientY;
				}
			});
		}

		for (let i = 0; i < this.count; i++) {
			let x = Math.floor(Math.random() * this.canvas.width),
				y = Math.floor(Math.random() * this.canvas.height),
				size = Math.random() * 3 + this.size,
				speed = Math.random() * 1 + this.speed,
				opacity = Math.random() * 0.5 + this.opacity;

			this.flakes.push({
				speed: speed,
				velY: speed,
				velX: 0,
				x: x,
				y: y,
				size: size,
				stepSize: Math.random() / 30,
				step: 0,
				angle: 180,
				opacity: opacity
			});
		}
		let imgList = document.querySelectorAll(".lis_flake");
		imgList.forEach(element => {
			this.imageItems.push(element);
		});
		this.imageNum = imgList.length;
		this.snow();
	}

	private snow(): void {
		this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);

		for (let i = 0; i < this.count; i++) {
			let flake = this.flakes[i],
				x = this.mX,
				y = this.mY,
				minDist = 100,
				x2 = flake.x,
				y2 = flake.y;

			let dist = Math.sqrt((x2 - x) * (x2 - x) + (y2 - y) * (y2 - y)),
				dx = x2 - x,
				dy = y2 - y;

			if (dist < minDist) {
				let force = minDist / (dist * dist),
					xcomp = (x - x2) / dist,
					ycomp = (y - y2) / dist,
					deltaV = force / 2;

				flake.velX -= deltaV * xcomp;
				flake.velY -= deltaV * ycomp;
			} else {
				flake.velX *= 0.98;
				if (flake.velY <= flake.speed) {
					flake.velY = flake.speed;
				}

				switch (this.windPower) {
					case false:
					case 0:
						flake.velX +=
							Math.cos((flake.step += 0.05)) * flake.stepSize;
						break;

					default:
						flake.velX += 0.01 + this.windPower / 100;
						break;
				}
			}
			let s = this.color;
			let patt = /^#([\da-fA-F]{2})([\da-fA-F]{2})([\da-fA-F]{2})$/;
			let matches: any = patt.exec(s);
			let rgb = `${parseInt(matches[1], 16)},${parseInt(
				matches[2],
				16
			)},${parseInt(matches[3], 16)}`;

			flake.y += flake.velY;
			flake.x += flake.velX;

			if (flake.y >= this.canvas.height || flake.y <= 0) {
				this.reset(flake);
			}
			if (flake.x >= this.canvas.width || flake.x <= 0) {
				this.reset(flake);
			}

			if (this.imgs.length == 0) {
				this.ctx.fillStyle = `rgba(${rgb},${flake.opacity})`;
				this.ctx.beginPath();
				this.ctx.arc(flake.x, flake.y, flake.size, 0, Math.PI * 2);
				this.ctx.fill();
			} else {
				let imgItem = this.imageItems[i % this.imageNum];
				if (this.IsImageOk(imgItem)) {
					this.ctx.drawImage(
						imgItem,
						flake.x,
						flake.y,
						flake.size * 2,
						flake.size * 2
					);
				}
			}
		}
		this.reqId = requestAnimationFrame(this.snow);
	}

	private reset(flake: any): void {
		if (this.windPower == false || this.windPower == 0) {
			flake.x = Math.floor(Math.random() * this.canvas.width);
			flake.y = 0;
		} else {
			if (this.windPower > 0) {
				let xarray = Array(
					Math.floor(Math.random() * this.canvas.width),
					0
				);
				let yarray = Array(
					0,
					Math.floor(Math.random() * this.canvas.height)
				);
				let allarray = Array(xarray, yarray);
				let selected_array =
					allarray[Math.floor(Math.random() * allarray.length)];

				flake.x = selected_array[0];
				flake.y = selected_array[1];
			} else {
				let xarray = Array(
					Math.floor(Math.random() * this.canvas.width),
					0
				);
				let yarray = Array(
					this.canvas.width,
					Math.floor(Math.random() * this.canvas.height)
				);
				let allarray = Array(xarray, yarray);
				let selected_array =
					allarray[Math.floor(Math.random() * allarray.length)];
				flake.x = selected_array[0];
				flake.y = selected_array[1];
			}
		}

		flake.size = Math.random() * 3 + this.size;
		flake.speed = Math.random() * 1 + this.speed;
		flake.velY = flake.speed;
		flake.velX = 0;
		flake.opacity = Math.random() * 0.5 + this.opacity;
	}

	private IsImageOk(item: any): boolean {
		if (!item || !item.complete) return false;

		if (typeof item.naturalWidth !== "undefined" && item.naturalWidth === 0)
			return false;

		return true;
	}
}
</script>

<style lang="less" scoped>
.snow-wrap {
	position: fixed;
	left: 0;
	top: 0;
	z-index: 9999;
	pointer-events: none;
	height: 100%;
	width: 100%;
	background: #000;
	canvans {
		display: block;
		height: 100%;
		width: 100%;
	}
}
</style>